#include "stdafx.h"
#include "Socket.h"
#include "System.h"

namespace Core
{

	Socket::Socket(SOCKET p_SocketDescriptor)
	{
		if(p_SocketDescriptor==0) {m_SocketDescriptor = 0; return;}
		m_SocketDescriptor = p_SocketDescriptor;
		m_Protocol = Protocol_Tcp;
		SetSockOption();
		m_RemoteAddress = System::GetPeerName((int)m_SocketDescriptor);
		m_Active = true;
	}

	Socket::Socket(Protocols p_Protocol)
	{
		Init(p_Protocol);
		m_RemoteAddress = System::GetPeerName((int)m_SocketDescriptor);
		m_Active = true;
	}

	void Socket::Init(Protocols p_Protocol)
	{
		if(m_Active) Close();
	#ifdef WIN32
		WSADATA wsaData;
		if (WSAStartup(MAKEWORD(2, 0), &wsaData) != 0)
		{
			Error();
		}
	#endif
		m_Protocol = p_Protocol;
		if (m_Protocol == Protocol_Tcp)
		{
			m_SocketDescriptor = socket(PF_INET, SOCK_STREAM, 0);
		}
		else if (m_Protocol == Protocol_Udp)
		{
			m_SocketDescriptor = socket(PF_INET, SOCK_DGRAM, 0);
		}
		SetSockOption();
		m_Active = true;
	}

	void Socket::SetSockOption()
	{
	#ifdef WIN32
		char t_Optval = 1;
	#else
		int t_Optval = 1;
	#endif
		if (setsockopt(m_SocketDescriptor, SOL_SOCKET, SO_REUSEADDR, &t_Optval, sizeof(t_Optval))==-1)
		{
			Error();
		}
		if (m_Protocol == Protocol_Tcp)
		{
			if (setsockopt(m_SocketDescriptor, SOL_SOCKET, SO_KEEPALIVE, &t_Optval, sizeof(t_Optval))==-1)
			{
				Error();
			}
		}
		else if (m_Protocol == Protocol_Udp)
		{
			if (setsockopt(m_SocketDescriptor, SOL_SOCKET, SO_BROADCAST, &t_Optval, sizeof(t_Optval))==-1)
			{
				Error();
			}
		}
	}

	void Socket::Error()
	{
		throw SocketException();
	}

	Socket::~Socket()
	{
	}

	void Socket::Bind(InternetAddress p_InternetAddress)
	{
		int t_Retval;
		m_LocalAddress = p_InternetAddress;
		sockaddr_in t_SockAddrIn = m_LocalAddress.GetSockAddrIn();
		t_Retval = bind(m_SocketDescriptor, (sockaddr*) &t_SockAddrIn, sizeof(sockaddr));
		if (t_Retval<0)
		{
			Error();
		}
	}

	void Socket::Connect(InternetAddress p_InternetAddress)
	{
		int t_Retval;
		m_RemoteAddress = p_InternetAddress;
		sockaddr_in t_SockAddrIn = m_RemoteAddress.GetSockAddrIn();
		t_Retval = connect(m_SocketDescriptor, (sockaddr*) &t_SockAddrIn, sizeof(sockaddr));
		if (t_Retval<0)
		{
			Error();
		}
		m_Active = true;
	}

	void Socket::Listen(int p_QueueNumber)
	{
		int t_Retval;
		t_Retval = listen(m_SocketDescriptor, p_QueueNumber);
		if (t_Retval<0)
		{
			Error();
		}
	}

	Socket Socket::Accept(int p_TimeOutMilliSeconds)
	{
		if((p_TimeOutMilliSeconds>=0) && !System::IsFDReady((int)m_SocketDescriptor, FDType_Read, p_TimeOutMilliSeconds)) return 0;
	#ifdef WIN32
		int t_SinSize = sizeof(sockaddr_in);
	#else
		SOCKET t_SinSize = sizeof(sockaddr_in);
	#endif
		sockaddr_in t_SockAddrIn = m_RemoteAddress.GetSockAddrIn();
		SOCKET t_SocketDesc = accept(m_SocketDescriptor, (sockaddr*) &t_SockAddrIn, &t_SinSize);
		if (t_SocketDesc==0xFFFFFFFF)
		{
			Error();
		}
		Socket t_Socket(t_SocketDesc);
		return t_Socket;
	}

	bool Socket::Send(RawData& p_RawData, int p_TimeOutMilliSeconds)
	{
		if((p_TimeOutMilliSeconds>=0) && !System::IsFDReady((int)m_SocketDescriptor, FDType_Write, p_TimeOutMilliSeconds)) return false;
		p_RawData.ResetReadIterator();
		int t_Retval;
		unsigned char t_Data[DATASIZE];
		unsigned char* t_PData = t_Data;
		p_RawData.WriteToMemory(t_PData, p_RawData.GetLength());
		t_Retval = send(m_SocketDescriptor, (char *) t_Data, p_RawData.GetLength(), 0);
		if (t_Retval<=0)
		{
			Error();
		}
		return true;
	}

	bool Socket::Receive(RawData& p_RawData, int p_TimeOutMilliSeconds)
	{
		if((p_TimeOutMilliSeconds>=0) && !System::IsFDReady((int)m_SocketDescriptor, FDType_Read, p_TimeOutMilliSeconds)) return false;
		p_RawData.ResetWriteIterator();
		int t_Retval;
		unsigned char t_Data[DATASIZE];
		unsigned char* t_PData = t_Data;
		t_Retval = recv(m_SocketDescriptor, (char*) t_Data, p_RawData.GetSize(), 0);
		p_RawData.ReadFromMemory(t_PData, t_Retval);
		if (t_Retval<=0)
		{
			Error();
		}
		return true;
	}

	bool Socket::SendTo(RawData& p_RawData, InternetAddress& p_InternetAddress, int p_TimeOutMilliSeconds)
	{
		if((p_TimeOutMilliSeconds>=0) && !System::IsFDReady((int)m_SocketDescriptor, FDType_Write, p_TimeOutMilliSeconds)) return false;
		p_RawData.ResetReadIterator();
		int t_Retval;
		unsigned char t_Data[DATASIZE];
		unsigned char* t_PData = t_Data;
		p_RawData.WriteToMemory(t_PData, p_RawData.GetLength());
		sockaddr_in t_SockAddrIn = p_InternetAddress.GetSockAddrIn();
		t_Retval = sendto(m_SocketDescriptor, (char*) t_Data, p_RawData.GetLength(), 0, (sockaddr *) &t_SockAddrIn, sizeof(sockaddr));
		if(t_Retval<=0)
		{
			Error();
		}
		return true;
	}

	bool Socket::ReceiveFrom(RawData& p_RawData, InternetAddress& p_InternetAddress, int p_TimeOutMilliSeconds)
	{
	    if((p_TimeOutMilliSeconds>=0) && !System::IsFDReady((int)m_SocketDescriptor, FDType_Read, p_TimeOutMilliSeconds)) return false;
		p_RawData.ResetWriteIterator();
	#ifdef WIN32
		int t_FromLength = sizeof(sockaddr);
	#else
		socklen_t t_FromLength = sizeof(sockaddr);
	#endif
		int t_Retval;
		unsigned char t_Data[DATASIZE];
		unsigned char* t_PData = t_Data;
		sockaddr_in t_SockAddrIn;
		t_Retval = recvfrom(m_SocketDescriptor, (char*) t_Data, p_RawData.GetSize(), 0, (sockaddr *) &t_SockAddrIn, &t_FromLength);
		p_InternetAddress.SetSockAddrIn(t_SockAddrIn);
		p_RawData.ReadFromMemory(t_PData, t_Retval);
		if(t_Retval<=0)
		{
			Error();
		}
		return true;
	}

	InternetAddress Socket::GetRemoteAddress()
	{
		return m_RemoteAddress;
	}

	InternetAddress Socket::GetLocalAddress()
	{
		return m_LocalAddress;
	}

	SOCKET Socket::GetSocketDescriptor()
	{
		return m_SocketDescriptor;
	}

	void Socket::Close()
	{
		if(!m_Active)
		{
			return;
		}
	#ifdef WIN32
		//WSACleanup();
		closesocket(m_SocketDescriptor);
	#else
		close(m_SocketDescriptor);
	#endif
		m_Active = false;
	}

	bool Socket::IsActive()
	{
		return m_Active;
	}

	void Socket::JoinGroup(const InternetAddress& pGroup) {
        struct ip_mreq mreq;
        mreq.imr_multiaddr.s_addr=inet_addr(pGroup.Address.c_str());
        mreq.imr_interface.s_addr=htonl(INADDR_ANY);
        if (setsockopt(GetSocketDescriptor(),IPPROTO_IP,IP_ADD_MEMBERSHIP,&mreq,sizeof(mreq)) < 0) {
            Error();
        }
	}

}
